﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using DevExpress.Utils;
using DevExpress.XtraEditors;
using DevExpress.XtraEditors.Controls;
using DevExpress.XtraEditors.Repository;
using DevExpress.XtraGrid;
using DevExpress.XtraGrid.Columns;
using DevExpress.XtraGrid.Views.Base;
using DevExpress.XtraGrid.Views.Grid;
using CSW.Framework.Common.Binding.Interfaces;
using CSW.Framework.Common.Utils;
using CSW.Framework.Linq;
using CSW.Framework.UI.Binding;
using CSW.Framework.UI.DevEx.UserControls;
using Guard = CSW.Framework.Common.Guard;

namespace CSW.Framework.UI.DevEx
{
    /// <summary>
    /// Typed Grid Controller.  Type safe access to <see cref="DXGridControl" />.
    /// </summary>
    /// <typeparam name="TItem">The type implementing <see cref="INotifyPropertyChanged" />.</typeparam>
    public class TypedGridController<TItem> : ITypedGridController<TItem>
        where TItem : class, INotifyPropertyChanged
    {
        private const BindingFlags m_PublicInstanceBindingFlags = BindingFlags.Public | BindingFlags.Instance;

        private readonly DXGridControl m_GridControl;
        private readonly GridView m_GridView;

        /*/// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        public TypedGridController(BaseGridControl gridControl)
            : this(gridControl, null, true)
        {
        }*/

        /*/// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="multiSelect">if set to <c>true</c>, multi select is enabled (default = true).</param>
        public TypedGridController(BaseGridControl gridControl, bool multiSelect)
            : this(gridControl, null, multiSelect)
        {
        }*/

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="exportFileName">Name of the export file, minus the extension.</param>
        public TypedGridController(DXGridControl gridControl, string exportFileName)
            : this(gridControl, exportFileName, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TypedGridControllerLinq{TItem}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="exportFileName">Name of the export file, minus the extension.</param>
        /// <param name="multiSelect">if set to <c>true</c>, multi select is enabled (default = true).</param>
        public TypedGridController(DXGridControl gridControl, string exportFileName, bool multiSelect)
        {
            Guard.ArgumentNotNull(gridControl, "gridControl");
            Guard.ArgumentNotNull(gridControl.DefaultView, "gridControl.DefaultView");
            Guard.ArgumentNotNullOrEmptyString(exportFileName, "exportFileName");

            m_GridControl = gridControl;
            m_GridView = (GridView)m_GridControl.DefaultView;

            m_GridControl.ExportFileName = exportFileName;
            m_GridView.OptionsSelection.MultiSelect = multiSelect;

            m_GridControl.WireViewEvents();

            m_GridView.OptionsView.ShowGroupPanel = false;
            m_GridView.OptionsView.ColumnAutoWidth = false;
            m_GridView.OptionsBehavior.Editable = false;
            m_GridView.OptionsSelection.EnableAppearanceFocusedCell = false;
            m_GridView.FocusRectStyle = DrawFocusRectStyle.RowFocus;
            m_GridView.OptionsView.EnableAppearanceOddRow = true;
            m_GridView.OptionsView.ShowIndicator = false;
            m_GridView.OptionsBehavior.AutoPopulateColumns = false;
            m_GridView.OptionsDetail.EnableMasterViewMode = false;
            m_GridView.ShowButtonMode = ShowButtonModeEnum.ShowAlways;
            m_GridView.BestFitMaxRowCount = 100;

            m_GridControl.TabStop = false;
        }

        /// <summary>
        /// Gets the selected object if one and only one row is selected; otherwise, returns <c>null</c>.
        /// </summary>
        /// <returns>The selected object if one and only one row is selected; otherwise, returns <c>null</c>.</returns>
        public TItem GetSelectedObject()
        {
            int? rowHandle = GetSelectedRowHandle();
            if (rowHandle == null)
                return null;

            return GetRow(rowHandle.Value);
        }

        /// <summary>
        /// Gets the selected row handle if one and only one data row is selected.
        /// </summary>
        /// <returns>The selected row handle if one and only one data row is selected; otherwise, <c>null</c>.</returns>
        public int? GetSelectedRowHandle()
        {
            int[] rowHandles = m_GridView.GetSelectedRows();

            int rowHandle;
            if (rowHandles.Length == 1)
                rowHandle = rowHandles[0];
            else if (rowHandles.Length == 0)
                rowHandle = m_GridView.FocusedRowHandle;
            else
                return null;

            if (!m_GridView.IsDataRow(rowHandle))
                return null;

            return rowHandle;
        }

        /// <summary>
        /// Gets the selected objects.
        /// </summary>
        /// <returns>The selected objects.</returns>
        public List<TItem> GetSelectedObjects()
        {
            int[] rowHandles = m_GridView.GetSelectedRows();

            List<TItem> list = new List<TItem>();

            foreach (int rowHandle in rowHandles)
            {
                if (!m_GridView.IsDataRow(rowHandle))
                    continue;

                TItem item = GetRow(rowHandle);
                list.Add(item);
            }

            return list;
        }

        /// <summary>
        /// Selects a single row.
        /// </summary>
        /// <param name="item">The item.</param>
        public void SelectSingleRow(TItem item)
        {
            Guard.ArgumentNotNull(item, "item");

            int rowHandle = m_GridView.GetRowHandle(DataSource.IndexOf(item));
            m_GridView.ClearSelection();
            m_GridView.SelectRow(rowHandle);
            m_GridView.MakeRowVisible(rowHandle, false);
        }

        /// <summary>
        /// Adds a row to the selection.
        /// </summary>
        /// <param name="item">The item.</param>
        public void SelectRow(TItem item)
        {
            Guard.ArgumentNotNull(item, "item");

            int rowHandle = m_GridView.GetRowHandle(DataSource.IndexOf(item));
            m_GridView.SelectRow(rowHandle);
            m_GridView.MakeRowVisible(rowHandle, false);
        }

        /// <summary>
        /// Deselects a row.
        /// </summary>
        /// <param name="item">The item.</param>
        public void DeselectRow(TItem item)
        {
            int rowHandle = m_GridView.GetRowHandle(DataSource.IndexOf(item));
            m_GridView.UnselectRow(rowHandle);
        }

        #region <<< AddField Methods >>>

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField(string caption, string fieldName)
        {
            return AddField(caption, fieldName, null, 65);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="field">Field expression.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(Expression<Func<TItem, TResult>> field)
        {
            string fieldName = GetFieldName(field);
            string caption = Common.Utils.StringUtils.StringConverter.GetPrettyName(fieldName);
            return AddField(caption, GetFieldName(field));
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="field">Field expression.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(string caption, Expression<Func<TItem, TResult>> field)
        {
            return AddField(caption, GetFieldName(field));
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="width">The width.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField(string caption, string fieldName, int width)
        {
            return AddField(caption, fieldName, null, width);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="field">Field expression.</param>
        /// <param name="width">The width.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(string caption, Expression<Func<TItem, TResult>> field, int width)
        {
            return AddField(caption, GetFieldName(field), width);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="repositoryItem">The repository item.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField(string caption, string fieldName, RepositoryItem repositoryItem)
        {
            return AddField(caption, fieldName, null, 65, repositoryItem);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="field">Field expression.</param>
        /// <param name="repositoryItem">The repository item.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(string caption, Expression<Func<TItem, TResult>> field, RepositoryItem repositoryItem)
        {
            return AddField(caption, GetFieldName(field), repositoryItem);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="formatString">The format string.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField(string caption, string fieldName, string formatString)
        {
            return AddField(caption, fieldName, formatString, 65);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="field">Field expression.</param>
        /// <param name="formatString">The format string.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(string caption, Expression<Func<TItem, TResult>> field, string formatString)
        {
            return AddField(caption, GetFieldName(field), formatString);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="formatString">The format string.</param>
        /// <param name="width">The width.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField(string caption, string fieldName, string formatString, int width)
        {
            return AddField(caption, fieldName, formatString, width, null);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="field">Field expression.</param>
        /// <param name="formatString">The format string.</param>
        /// <param name="width">The width.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(string caption, Expression<Func<TItem, TResult>> field, string formatString, int width)
        {
            return AddField(caption, GetFieldName(field), formatString, width);
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="formatString">The format string.</param>
        /// <param name="width">The width.</param>
        /// <param name="repositoryItem">The repository item.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField(string caption, string fieldName, string formatString, int width, RepositoryItem repositoryItem)
        {
            bool isPrimitive = true;
            PropertyInfo propertyInfo = typeof(TItem).GetProperty(fieldName, m_PublicInstanceBindingFlags);
            HorzAlignment horzAlignment = HorzAlignment.Near;
            if (propertyInfo != null)
            {
                if (propertyInfo.PropertyType == typeof(bool) ||
                    propertyInfo.PropertyType == typeof(bool?))
                {
                    if (repositoryItem == null)
                    {
                        repositoryItem = new RepositoryItemCheckEdit();
                        m_GridControl.RepositoryItems.Add(repositoryItem);
                        repositoryItem.AutoHeight = false;
                        repositoryItem.Name = "repositoryItemCheckEdit" + fieldName;
                        repositoryItem.EditValueChanged += delegate { m_GridView.CloseEditor(); };
                    }
                }
                else if (NumberHelper.IsNumericType(propertyInfo.PropertyType))
                {
                    horzAlignment = HorzAlignment.Far;
                }
                else if (propertyInfo.PropertyType != typeof(string) &&
                         propertyInfo.PropertyType != typeof(DateTime) &&
                         propertyInfo.PropertyType != typeof(DateTime?))
                {
                    isPrimitive = false;
                }
            }

            GridColumn gridColumn = new GridColumn();
            gridColumn.Caption = caption;
            gridColumn.FieldName = fieldName;
            gridColumn.Visible = true;
            gridColumn.VisibleIndex = m_GridView.Columns.Count;
            gridColumn.AppearanceCell.TextOptions.HAlignment = horzAlignment;
            gridColumn.AppearanceCell.Options.UseTextOptions = true;
            if (!isPrimitive)
            {
                gridColumn.SortMode = ColumnSortMode.DisplayText;
                gridColumn.OptionsColumn.AllowSort = DefaultBoolean.True;
                gridColumn.FilterMode = ColumnFilterMode.DisplayText;
            }
            if (!string.IsNullOrEmpty(formatString))
            {
                gridColumn.DisplayFormat.FormatString = formatString;
                gridColumn.DisplayFormat.FormatType = FormatType.Custom;
            }
            if (repositoryItem != null)
            {
                if (repositoryItem is RepositoryItemComboBox)
                {
                    RepositoryItemComboBox cbo = (RepositoryItemComboBox)repositoryItem;
                    repositoryItem.EditValueChanged += delegate 
                    {
                        if (cbo.TextEditStyle == TextEditStyles.DisableTextEditor)
                            m_GridView.CloseEditor(); 
                    };
                }
                gridColumn.ColumnEdit = repositoryItem;
            }
            gridColumn.Width = width;
            m_GridView.Columns.Add(gridColumn);

            return gridColumn;
        }

        /// <summary>
        /// Adds a field.
        /// </summary>
        /// <param name="caption">The caption.</param>
        /// <param name="field">Field expression.</param>
        /// <param name="formatString">The format string.</param>
        /// <param name="width">The width.</param>
        /// <param name="repositoryItem">The repository item.</param>
        /// <returns>The newly created <see cref="GridColumn" />.</returns>
        public GridColumn AddField<TResult>(string caption, Expression<Func<TItem, TResult>> field, string formatString, int width, RepositoryItem repositoryItem)
        {
            return AddField(caption, GetFieldName(field), formatString, width, repositoryItem);
        }

        /// <summary>
        /// Gets the name of the field from a field expression.
        /// </summary>
        /// <param name="field">Field expression.</param>
        /// <returns>The name of the field.</returns>
        private static string GetFieldName(LambdaExpression field)
        {
            return LinqToSqlHelper.GetFieldName(field);
        }

        #endregion <<< AddField Methods >>>

        /// <summary>
        /// Gets or sets the data source.
        /// </summary>
        /// <value>The data source.</value>
        public virtual BindingList<TItem> DataSource
        {
            get
            {
                if (m_GridControl.DataSource is BindingList<TItem>)
                    return (BindingList<TItem>)m_GridControl.DataSource;
                else if (m_GridControl.DataSource is ILinkedBindingList<TItem>)
                    return ((ILinkedBindingList<TItem>)m_GridControl.DataSource).GetLinkedBindingList();
                else if (m_GridControl.DataSource == null)
                    return null;

                throw new NotSupportedException(string.Format("DataSource is of type '{0}'.", m_GridControl.DataSource.GetType()));
            }
            set
            {
                m_GridControl.DataSource = value;
            }
        }

        /// <summary>
        /// Best fits the columns.
        /// </summary>
        public void BestFitColumns()
        {
            m_GridView.BestFitColumns();
        }

        /// <summary>
        /// Gets or sets the number of data rows whose contents are processed to apply best fit to columns.
        /// </summary>
        public int BestFitMaxRowCount
        {
            get { return m_GridView.BestFitMaxRowCount; }
            set { m_GridView.BestFitMaxRowCount = value; }
        }

        /// <summary>
        /// Clears the selection.
        /// </summary>
        public void ClearSelection()
        {
            m_GridView.ClearSelection();
        }

        /// <summary>
        /// Gets the data source index of a corresponding row handle.
        /// </summary>
        /// <param name="rowHandle">The row handle.</param>
        /// <returns>The data source index.</returns>
        public int GetDataSourceRowIndex(int rowHandle)
        {
            return m_GridView.GetDataSourceRowIndex(rowHandle);
        }

        /// <summary>
        /// Gets a cell's display text.
        /// </summary>
        /// <param name="rowHandle">The row handle.</param>
        /// <param name="gridColumn">The grid column.</param>
        /// <returns>The display text.</returns>
        public string GetRowCellDisplayText(int rowHandle, GridColumn gridColumn)
        {
            return m_GridView.GetRowCellDisplayText(rowHandle, gridColumn);
        }

        /// <summary>
        /// Gets the row count.
        /// </summary>
        /// <value>The row count.</value>
        public int RowCount
        {
            get { return m_GridView.RowCount; }
        }

        /// <summary>
        /// Gets a row of a corresponding row handle.
        /// </summary>
        /// <param name="rowHandle">The row handle.</param>
        /// <returns>The row.</returns>
        public TItem GetRow(int rowHandle)
        {
            if (m_GridView.IsDataRow(rowHandle))
            {
                object item = m_GridView.GetRow(rowHandle);

                if (item is TItem)
                    return (TItem)item;

                if (item is IExtensionOf<TItem>)
                    return ((IExtensionOf<TItem>)item).GetObject();

                throw new InvalidOperationException(string.Format("Item at rowHandle '{0}' is not of type '{1}'.", rowHandle, typeof(TItem)));
            }

            return null;
        }

        /// <summary>
        /// Gets the <see cref="DXGridControl" />.
        /// </summary>
        /// <value>The <see cref="DXGridControl" />.</value>
        public DXGridControl Grid
        {
            get { return m_GridControl; }
        }

        /// <summary>
        /// Gets the <see cref="GridView" />.
        /// </summary>
        /// <value>The <see cref="GridView" />.</value>
        public GridView GridView
        {
            get { return m_GridView; }
        }

        /// <summary>
        /// Adds a record count to the bottom of the grid.  Uses the embedded navigator.
        /// </summary>
        public void AddRecordCount()
        {
            m_GridControl.UseEmbeddedNavigator = true;
            foreach (NavigatorButton button in m_GridControl.EmbeddedNavigator.Buttons.ButtonCollection)
                button.Visible = false;
            m_GridControl.EmbeddedNavigator.TextStringFormat = "{1:#,0} record(s)";
            m_GridControl.EmbeddedNavigator.TextLocation = NavigatorButtonsTextLocation.Begin;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
        }
    }
}
